package buildinjava.io;

import buildinjava.BuildException;
import java.io.File;
import java.io.FileFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Deletes resources.
 *
 * @author Gili Tzabari
 */
public final class Delete
{
	private final Logger log = LoggerFactory.getLogger(Delete.class);
	private static final int retryCount = 3;
	private static final int retryDelay = 1000;
	private FileFilter stepInto;
	private FileFilter toDelete;

	/**
	 * Creates a new Delete operation.
	 */
	public Delete()
	{
		this.stepInto = FileFilters.isDirectory();
		this.toDelete = FileFilters.acceptAll();
	}

	/**
	 * Indicates what files should be processed.
	 *
	 * @param stepInto indicates what directories to step into. By default all directories are
	 * included. stepInto does not need to check that the path is actually a directory.
	 * @param toDelete indicates what paths should be processed. By default all paths are included.
	 * @return the Delete object
	 * @throws IllegalArgumentException if stepInto or toDelete are null
	 */
	public Delete filter(FileFilter stepInto, FileFilter toDelete) throws IllegalArgumentException
	{
		if (stepInto == null)
			throw new IllegalArgumentException("stepInto may not be null");
		if (toDelete == null)
			throw new IllegalArgumentException("toDelete may not be null");
		this.stepInto = FileFilters.intersection(FileFilters.isDirectory(), stepInto);
		this.toDelete = toDelete;
		return this;
	}

	/**
	 * Deletes a file.
	 *
	 * @param file the file to delete, implicitly accepted by the filter
	 * @throws BuildException if the operation fails
	 */
	public void file(final File file) throws BuildException
	{
		if (log.isDebugEnabled())
			log.debug(toString());
		if (file.isDirectory())
			delete(file, stepInto, toDelete);
		else
		{
			try
			{
				boolean done = false;
				for (int i = 0; i < retryCount; ++i)
				{
					if (file.exists() && !file.delete())
					{
						if (log.isWarnEnabled())
							log.warn("Retrying deletion of " + file.getAbsolutePath());
						Thread.sleep(retryDelay);
						continue;
					}
					done = true;
					break;
				}
				if (!done)
					throw new BuildException("Cannot delete " + file.getAbsolutePath());
			}
			catch (InterruptedException e)
			{
				throw new BuildException(e);
			}
		}
	}

	/**
	 * Deletes a single file.
	 *
	 * @param file the file to delete
	 * @param stepInto indicates what directories to step into
	 * @param toDelete indicates what files to delete
	 * @throws BuildException if an expected build error occurs
	 */
	private void delete(File file, FileFilter stepInto, FileFilter toDelete) throws BuildException
	{
		for (File nestedFile: file.listFiles(stepInto))
			delete(nestedFile, stepInto, toDelete);
		for (File nestedFile: file.listFiles(toDelete))
		{
			if (log.isTraceEnabled())
				log.trace("Deleting " + nestedFile.getPath());
			try
			{
				boolean done = false;
				for (int i = 0; i < retryCount; ++i)
				{
					if (nestedFile.delete())
					{
						done = true;
						break;
					}
					if (log.isWarnEnabled())
						log.warn("Retrying deletion of " + nestedFile.getAbsolutePath());
					Thread.sleep(retryDelay);
				}
				if (!done)
					throw new BuildException("Cannot delete " + nestedFile.getAbsolutePath());
			}
			catch (InterruptedException e)
			{
				throw new BuildException(e);
			}
		}
	}

	@Override
	public String toString()
	{
		return getClass().getName() + "[stepInto=" + stepInto + ", toDelete=" + toDelete + "]";
	}
}
